- 5 - ВВЕДЕНИЕ Компьютерные игры..................................... 18 Об этой книге......................................... 18 Что вы должны знать................................... 19 Как организована эта книга............................ 19 1.ВИДЕОИГРЫ, ПЕРВЫЕ ШАГИ ... _________________________ 21 Кто пишет видеоигры ?................................. 22 Откуда берутся идеи ? ................................ 22 Фазы создания видеоигр ............................... 22 Что вы узнаете из этой книги ? ....................... 24 Итог ................................................. 24 2. ОСНОВЫ ЯЗЫКА АССЕМБЛЕРА ___________________________ 25 Зачем нам нужен ассемблер при написании игр ? ....... 26 Обзор семейства процессоров 80х86 .................... 26 Регистры процессора .................................. 27 Сегментные регистры .............................. 28 Флаговый регистр ................................ 28 Общий вид ассемблерной функции ...................... 28 Передача параметров ............................. 30 Директива USES .................................. 33 Передача указателей ............................. 35 Локальные переменные ............................ 36 Директива LOCAL ................................. 37 Создание внешних ссылок ............................. 38 Возвращение параматров в вызывающую функцию ......... 40 - 6 - Установка видеорежимов .............................. 41 Сверхскоростная очистка экрана ....................... 42 Использование встроенного (in-line) ассемблера ....... 46 Итог ................................................. 48 3 ОСНОВЫ УСТРОЙСТВ ВВОДА______________________________ 49 Взаимодействие с пользователем в видеоиграх .......... 49 Джойстик ............................................. 50 Как работает джойстик ........................... 51 Кнопки джойстика ................................ 52 Чтение позиции джойстика ........................ 54 Калибровка джойстика ............................ 56 Клавиатура ........................................... 63 Скан-коды ....................................... 64 Статус клавиш ................................... 66 Получение скан-кодов с клавиатуры ............... 67 Получение ASCII-кодов с клавиатуры .............. 67 Теперь все вместе: Демонстрационная программа работы с клавиатурой ............................ 68 Мышь ................................................. 73 Итог ................................................. 80 4 МЕХАНИЗМЫ ДВУХМЕРНОЙ ГРАФИКИ ________________________81 Двухмерная проекция .................................. 81 Точки, линии и области ............................... 82 Точки ........................................... 83 Линии ........................................... 84 Многоугольники .................................. 86 Объекты .............................................. 88 Позиционирование объекта ........................ 89 Трансляция объектов ............................. 90 Масштабирование объектов ........................ 92 Вращение объектов ............................... 93 - 7 - Отсечения ....................................... 101 Матрицы .............................................. 103 Произведение операций над матрицами ............. 103 Единичная матрица ............................... 105 Использование матриц в играх ......................... 106 Главная матрица перемещений ..................... 106 Главная матрица масштабирования ................. 106 Главная матрица поворотов ....................... 107 Общая матрица масштабирования, поворотов и перемещений ..................................... 107 Компонент нормализации вершины .................. 107 Программа Астероиды с использованием матриц ..... 108 К вашему сведению .................................... 115 Основы контроля столкновений .................... 116 Итог ................................................. 117 5 СЕКРЕТЫ VGA-КАРТ __________________________________ 119 Обзор VGA-карт ....................................... 120 256-цветный режим .................................... 121 Конфигурация видеопамяти ............................. 122 Таблицы цветов ....................................... 122 Переопределение цветовой палитры ..................... 124 Цветовая ротация ..................................... 128 Рисование точки ...................................... 128 Рисование линий ...................................... 130 Графический формат РСХ ............................... 137 Побитовое копирование изображения(бит-блиттинг) ...... 145 Спрайты .............................................. 145 Отображение текста ................................... 152 Дублирующее буферизирование .......................... 155 Вертикальный обратный ход луча ....................... 156 Тайминг .............................................. 157 Игра Tombstone ....................................... 158 Итог ................................................. 172 - 8 - 6 ТРЕТЬЕ ИЗМЕРЕНИЕ __________________________________ 173 Что такое трехмерное пространство .................... 174 Точки, линии, многоугольники и объекты в трехмерном пространстве ............................ 175 Перемещения, масштабирование и повороты в трехмерном пространстве ............................ 180 Перемещение трехмерного объекта ................. 180 Масштабирование трехмерного объекта ............. 181 Вращение трехмерного объекта .................... 181 Последнее слово о трехмерных трансформациях ..... 182 Проекции ............................................. 182 Копирование на экран ............................ 184 Масштабирование экрана .......................... 187 Математические основы параллельных проекций ..... 187 Математические основы перспективной проекции .... 187 Объем просмотра ................................. 188 Геометрическое моделирование ......................... 189 Удаление невидимых поверхностей ................. 190 Алгоритм Художника .............................. 191 Алгоритм Z-буфера ............................... 195 Соотношение пространства образов и пространства объектов ............................197 Трассировка лучей ................................197 Отсечение лучей ................................. 199 Математические основы отсечения лучей ........... 205 Рисование лучей ................................. 205 Вычисление точки первого пересечения ............ 207 Вычисление оставшихся пересечений ............... 209 Вычисление расстояния ........................... 210 Вычисление масштаба ............................. 211 Уменьшение проекционных искажений ............... 212 Отрисовка фрегментов стен ...................... 213 Реализация отсекателя лучей ......................... 214 Оптимизация отсекателя лучей ........................ 223 - 9 - Отрисовка дверей ипрозрачных областей ............... 223 Освещение, тени и палитра ........................... 225 Рассеянное освещение ............................ 225 Локальное освещение ............................. 226 Закон Ламберта .................................. 226 Закон обратных квадратов ........................ 227 Создание модели освещения ....................... 227 Итог ................................................ 230 7 УЛУЧШЕННАЯ БИТОВАЯ ГРАФИКА И СПЕЦИАЛЬНЫЕ ЭФФЕКТЫ _____________________________ 231 Ускорение процесса двоичного кодового преобразования (бит-блиттинга) ............ 232 Применение логических операций ..................... 234 Кодирование прозрачности ........................... 239 Битовое отсечение .................................. 242 Контроль столкновения спрайтов ..................... 247 Дублирующая буферизация ............................ 248 Использование сигнала вертикальной синхронизации ... 252 Мультипликация с помощью регистров цвета ........... 255 Освещение ваших игр ................................ 260 Связь мультипликации с контекстом .................. 262 "Animotion" ........................................ 263 Прокрутка .......................................... 269 Специальные эффекты ................................ 275 Фоновая мультипликация ......................... 275 Экранные эффекты ............................... 276 Пробовали ли вы текстурировать ? ................... 283 Масштабирование растровых изображений .............. 284 Повороты объектов .................................. 291 Итог ............................................... 298 - 10 - 8 ВЫСОКОСКОРОСТНЫЕ ТРЕХМЕРНЫЕ СПРАЙТЫ ________________ 299 Механика трехмерных спрайтов ....................... 300 Аксонометрические преобразования ................... 303 Правильный расчет масштаба ......................... 304 Видимый объем ...................................... 305 Новая версия масштабирования ....................... 306 Отсечение спрайтов в трехмерном пространстве ....... 317 Построение траекторий .............................. 319 Удачный угол зрения на спрайты ..................... 321 Трехмерное звездное небо ........................... 327 Реализм ....................................... 328 Создание звездного неба с использованием целых чисел ................................... 328 Оцифровка объектов и моделирование ............ 342 Создание съемочного мини-павильона ............ 343 Video for Windows ..............................345 Цветовая палитра ...............................346 Эксперименты с макетами ....................... 348 Коротко! (То есть итог) ............................ 348 9 ЗВУКОВЫЕ ЭФФЕКТЫ И МУЗЫКА __________________________ 349 Основы звука ......................................... 350 Устройство и архитектура звуковой карты Sound Blaster ........................................ 354 Оцифрованный звук .................................... 356 Воспроизведение оцифрованного звука .............. 358 Использование функций драйвера для проигрывания VOC-файлов ........................................... 362 Частотный синтезатор ................................. 377 Музыка и MIDI ........................................ 378 Использование звука в играх .......................... 379 Итог ................................................. 382 10 РАЗРАБОТКА МУЗЫКИ ДЛЯ КОМПЬЮТЕРНЫХ ИГР ____________ 383 Способы воспроизведениязвука ....................... 388 Пакеты программ DIGPAK и MIDPAC .................... 392 - 11 - Что такое "расширенный MIDI-формат"? ............... 397 В каком формате должны быть представлены данные MIDI ? ...................................... 398 Вопросы и ответы ................................... 398 API пакета программ DIGPAK ......................... 399 API пакета программ MIDPAK ......................... 415 Завтрак с Толстяком - музыка в программах .......... 423 Почему ? ........................................... 423 Когда ? ............................................ 424 Как ? .............................................. 425 Создание пользовательского интерфейса с системой распознавания голоса .................... 425 Демонстрационная программа Test.c ...................427 11 АЛГОРИТМЫ, СТРУКТУРЫ ДАННЫХ И МЕТОДОЛОГИЯ ИГР ................................... 433 Структуры данных, используемые для представления игрового пространства .............................. 434 Когда объекты сталкиваются ......................... 440 Игровые объекты .................................... 442 Структуры данных в компьютерных играх .............. 443 Клонирование игровых объектов ...................... 444 Состояние бытия .................................... 446 Интерфейс пользователя ............................. 452 Демонстрационный режим ............................. 453 Сохранение игры .................................... 455 Моделирование реального мира ....................... 455 Итог ............................................... 464 12 МНИМОЕ ВРЕМЯ, ПРЕРЫВАНИЯ И МНОГОЗАДАЧНОСТЬ ........ 465 Восприятие игры .................................... 466 Что такое многозадачность ? ........................ 466 Реализация многозадачности при помощи прерываний ..................................... 469 Написание программы обработки прерываний на языке Си ........................................ 472 Установка обработчика прерывания ................... 473 - 12 - Игровой цикл ....................................... 477 Автономные функции ............................. 479 Функции ответа ................................. 481 Программируем системные часы ....................... 485 Изменение частоты таймера ...................... 486 Собираем все вместе ................................ 489 Пример обработчика прерывания N1 - Там полно звезд . 491 Пример обработчика прерывания N2 - Ловим нажатие клавиш! .................................... 496 Заключение ......................................... 502 13 ИСКУССТВЕННЫЙ ИНТЕЛЛЕКТ ___________________________ 503 Как мыслят видеоигры: обзор ........................ 504 Алгоритмы Преследования и Уклонения ................ 505 Преследование ................................... 505 Уклонение ....................................... 509 Шаблонные мысли ................................. 510 Случайные передвижения .......................... 517 Конечные автоматы .................................. 521 Конечный автомат, управляемый окружающей средой 522 Управление приоритетным состоянием .............. 530 Вероятностные автоматы .......................... 530 Память и обучение .................................. 536 Алгоритмы Поиска. Выслеживание игрока .............. 537 Теория игр ......................................... 538 Итог ............................................... 539 14 СВЯЗЬ _____________________________________________ 541 Средства связи видеоигр ............................ 542 Последовательный интерфейс ПК ...................... 543 Универсальный асинхронный приемопередатчик ......... 543 Установки и статус UART ..........................544 Аппаратное обеспечение UART...................... 549 Операции с ROM BIOS ................................ 550 - 13 - Соединение через нуль-модем ........................ 550 Построение коммуникационной библиотеки ............. 552 Инициализация последовательного порта ........... 552 Установка прерывания ............................ 555 Чтение символа из буфера ........................ 557 Запись в последовательный порт .................. 558 Коммуникационная программа: NLINK .................. 559 Стратегия игровых коммуникаций ..................... 566 Синхронизация вектора состояния .................... 569 Синхронизация состояния ввода/вывода ............... 570 Временная синхронизация ............................ 572 Модем .............................................. 573 Net Tank: танковый имитатор для двухигроков ........ 574 Анализ игры Net Tank ............................... 576 Раздел 1: Инициализация ......................... 576 Раздел 2: Игровой цикл .......................... 577 Раздел 3: Удаление объектов ..................... 577 Раздел 4: Получение входных данных и передача сотояния дистанционно управляемой системе ....... 577 Раздел 5: Перемещение объектов .................. 578 Раздел 6: Распознавание столкновений ............ 578 Раздел 7: Рисование объектов .................... 579 Раздел 8: Дублирующий буфер ..................... 579 Раздел 9:Всякая всячина ......................... 579 Раздел 10: Опять и снова опять .................. 579 Итог ................................................ 580 15 ИНСТРУМЕНТАЛЬНЫЕ СРЕДСТВА _________________________ 581 Определение понятия инструментального средства ...... 582 Редакторы изображений ............................ 583 Пакеты анимации ..................................... 584 Производство кинофильма ............................. 584 Звуковые редакторы .................................. 586 - 14 - Редактор поля WarEdit ............................... 586 Использование WarEdit ............................ 588 Функциональное описаниепрограммы WarEdit.......... 590 Улучшения WarEdit ................................ 595 Итог ................................................ 596 16 ГРАФИЧЕСКОЕ ОФОРМЛЕНИЕ ИГРЫ _______________________ 597 Удобные детали иинструменты ......................... 598 Первый этап: планирование ........................... 599 Разрешающая способность и количество цветов ...... 600 Для кого вы пишете игру ? ........................ 600 Содержание и общее настроение игры ............... 600 Что детализировать а что нет? .................... 600 Несколько слов по поводу цвета ................... 600 Разработка 256-цветовой палитры ..................... 602 Ну дайте мне место (разработка стен, пола и "кирпичиков" потолка) ............................... 608 Детали, детали и детали .......................... 608 Придание "кирпичикам" стен глубины трехмерного пространства ......................... 611 Масштабирование изображения ...................... 611 Создание бесшовных мозаичных изображений ......... 613 Особые ситуации .................................. 615 Удаление неровностей ............................. 620 Как избежать цветовых проблем во время игры ...... 620 Если б у меня было оружие и еда! (разработка объекта) 620 Соотношение размеров объектов и стен ............. 621 Помните о перспективе ............................ 621 Подайте сюда врагов! (разработка персонажей) ........ 621 Существует ли легкий путь? ....................... 622 Пропорции ........................................ 622 Основополагающие принципы для оживления персонажа ....................................... 622 Технические приемы анимации ...................... 623 Чудовища и прочие кровожадные персонажи .......... 623 - 15 - Последние дорисовки ................................. 624 Начальная и конечная заставки .................... 625 Оформление оценочного экрана ..................... 625 Экраны для отражения статуса и изменения конфигурации игры ................................ 626 Анимация ......................................... 626 Кадры для привлечения внимания ................... 626 Итог ................................................ 626 17 ТЕХНИКА СОЗДАНИЯ ПАРАЛЛАКСА _______________________ 627 Еще кое-что о режиме 13h ............................ 628 Примечания по поводу демонстрационной программы ..... 629 Первый шаг ...................................... 629 Несколько смещающихся слоев ......................... 639 "Прозрачные" пиксели ............................ 640 Оптимизированные версии OpaqueBlt() и TransparentBlt()................................. 647 Смещение мозаичных изображений ...................... 650 Устранение эффекта сдвига кадра ..................... 662 РСХ-графика ......................................... 663 Примечания по выполнению ............................ 663 Итог ................................................ 664 18 ТЕХНИКА ОПТИМИЗАЦИИ ______________________________ 665 Введение ............................................ 666 Передача параметров ................................. 666 Глобальные переменные ............................... 668 Указатели и использование псевдоимен ................ 668 Использование регистров ............................ 669 Оптимимзация компилятора ............................ 670 Развертка циклов .................................... 671 Бинарное умножение .................................. 673 Таблицы поиска ...................................... 674 Математика с фиксированной запятой .................. 678 Присваивание ..................................... 680 Сложение и вычитание ............................. 681 - 16 - Умножение ........................................ 681 Деление .......................................... 682 Точность ......................................... 682 Максимальное цифровое представление .............. 682 Встроенный ассемблер ................................ 685 Оптимизация рисования пикселей ...................... 685 Оптимизация изображаемой линии ...................... 688 Итог ................................................ 690 19 ИГРА WARLOCK (КОЛДУН) _____________________________ 691 Сюжет игры .......................................... 694 Компоненты игры ..................................... 694 Примечание по звуку .............................. 694 Загрузка и компиляция Warlock..................... 695 Режим демонстрации ............................... 696 Движение по игровому пространству ................ 696 Новый отсекатель лучей .............................. 697 Секция 1 ......................................... 703 Секция 2 ......................................... 703 Секция 3 ......................................... 703 Секция 4 ......................................... 703 Секция 5 ......................................... 704 Секция 6 ......................................... 704 Секция 7 ......................................... 704 Изображение текстуры ................................ 704 Оттенение ........................................... 708 Использование ассемблера ............................ 709 Цикл игры ........................................... 711 Игровое пространство ................................ 712 Режим демонстрации .................................. 714 Размещение объектов в пространстве .................. 715 Быстрее, быстрее и еще быстрее ...................... 716 Несколько слов напоследок ........................... 716 - 18 - ВВЕДЕНИЕ Как претворить в жизнь идею компьютерной игры? Приходилось ли вам, играя в свою любимую игру, мечтать о том, как можно было бы ее улучшить? Задумывались ли вы в процессе игры о том, как она устроена? Эта книга откроет вам путь к созданию собственных игр. КОМПЬЮТЕРНЫЕ ИГРЫ Если вы не играли в свою любимую аркадную игру и не убивали кучу монстров уже несколько месяцев - вы явно заработались! Вернитесь к играм и увидите, как вы помолодеете и как разгладятся морщины на вашем утомленном лице. Дни Hunt the Wampus и Lunar Lander миновали, и теперь игры стали намного ярче, красрчнее и хитрее. Игра сегодняшнего дня содержит целые мили туннелей, дорог и странных созданий.Они сопровождаются потрясающей графикой и звуком. DOOM - хороший пример такой игры. Она включает запутанные переходы и тайники с сокровищами, чудовищ, на которых вы можете охотиться и которых вы должны убить прежде, чем они убьют вас. Если вы хоть раз играли в DOOM, то не перестанете тосковать по кислотным лужам, ружьям, чудовищам, скрытым проходам, лифтам, взрывам ... этот список можно продолжать до бесконечности. Все это было создано благодаря комбинации графики и звукового сопровождения. Графика DOOM дает вам полное ощущение пространства и перспективы - как если бы вы действительно находились в центре событий. Для этого DOOM использует приемы работы с трехмерной графикой. Разработчики игр для ПК используют трехмерную графику для увеличения реалистичности игры любого типа - посмотрите на 7-th Guest, MYST, X-Wing, Outpost, Indy Car Racing. Часто трехмерная графика применяется и в имитаторах спортивных игр, таких как скачки или бокс. После добавления к такой графике сюжета и высококачественного звука, разработку игры можно считать законченной. Вы почувствуете себя реальным участником действия - стельбы, гонок и поиска сокровищ, прилагающим все усилия, чтобы остаться в живых. ОБ ЭТОЙ КНИГЕ Большинство книг по компьютерным играм объясняют, как в них играть. В отличие от них, эта книга учит, как соэдавать собственные игры, используя опыт, накопленный профессионалами. Профессиональные разработчики игр для ПК научат вас, как создавать игру, использующую высококачественную графику и звук. Если вы только начинаете карьеру разработчика компьютерных игр, эта книга окажется для вас незаменимым подспорьем. Она научит вас, как осуществлять прокрутку графики, оформлять таблицы лачших результатов, программировать трехмерную графику, - 19 - создавать текстуры и многому, многому другому. Если вы уже имеете опыт в разработке игр, эта книга поможет вам освоить массу новых технических приемов. ЧТО ВЫ ДОЛЖНЫ ЗНАТЬ Для понимания большинства материала, представленного в этой книге, вы должны хорошо владеть языком программирования Си. Большинство книг по созданию компьютерных игр ориентируются на ассемблер.В этой книге ассемблеру уделено совсем немного внимания, а основной акцент сделан на Си. Однако, она содержит несколько ассемблерных примеров, так что знакомство с этим языком вам не помешает. КАК ОРГАНИЗОВАНА ЭТА КНИГА Эта книга посвящена технике разработки игр для ПК. Она не является учебником, в котором материал представлен последовательно - от простого к сложному, однако в построении книги заметна определенная система. В первой главе, "Видеоигры, первые шаги ...", вы увидите, кто пишет игры, откуда берутся идеи и изучите основные этапы разработки игровой программы. Вторая глава, "Основы языка ассемблера", даст вам небольшой урок по ассемблеру. Вы изучите терминологию и методы, использованные при создании игр, представленных в этой книге. В третьей главе, "Основы устройств ввода", будет дан обзор методов построения интерфейса между играми и такими устройствами, как джойстик, мышь и клавиатура. Четвертая глава, "Механизмы двухмерной графики", покажет вам основные приемы, используемые при разработке двухмерных игр типа Commander Keen. Вы изучите технику вращения, масштабирования, перемещения и отсечения объектов в двухмерных играх. Глава пятая, "Секреты VGA-карт", посвящена работе с графикой VGA. Вы изучите 256-цветный режим, таблицу цветов, копирование битовых изображений и т.д. Шестая глава, "Третье измерение", укажет вам путь в трехмерный мир. Вы узнаете, как создавать DOOM-подобные миры, используя технику работы с трехмерной графикой. Седьмая глава, "Усовершенствованная битовая графика и специальные эффекты", позволит вам получить представление об ускоренном выводе изображений, работе со светотенью и другими спецэффектами. Будьте внимательны - отсюда начинаются боевики! Глава восьмая, "Высокоскоростные трехмерные спрайты", откроет вам дорогу в изумительный мир высокоскоростных трехмерных спрайтов. Эта глава - 20 - покажет, как создать реалистичную модель космического корабля и вставить ее в вашу программу. В девятой главе, "Звуковые эффекты и музыка", вы познакомитесь с основами применения звука в ваших играх. Вы сможете вызвать у игрока волнение и ужас, используя Sound Blaster. В игре музыка заставит ваше сердце биться в такт происходящему. В десятой главе, "Разработка музыки для компьютерных игр", вы изучите приемы работы с программами DIGPAK и MIDPAK, которые позволяют создавать впечатляющую музыку и звуковые эффекты. Благодаря этим программам вы сможете добавлять оркестровую музыку и необычайные звуковые эффекты в ваши творения, что придаст им профессиональный вид. Эти программы использовались при создании таких популярных игр, как 7-th Guest, Terminator 2029, Mechwarrior II. Глава 11-я, "Алгоритмы, структуры данных и методология видеоигр", знакомит вас с внутренней структурой профессиональных игр. Вы научитесь моделировать реальный мир в своих виртуальных вселенных. Двенадцатая глава, "Мнимое время, прерывания и многозадачность", научит вас добавлять новое измерение - время. Вы изучите приемы параллельной обработки различных игровых задач, создания главного игрового цикла и как использовать прерыванияпри работе с устройствами ввода/вывода. Глава 13-я, "Искусственный интеллект", поможет вам сделать своих персонажей более интеллектуальными. Вы же не хотите, чтобы они напрасно теряли время. Научите их думать и реагировать на ваши действия так, чтобы игру невозможно было отличить от реальности. Четырнадцатая глава, "Связь", научит вас добавлять дополнительные возможности. Вы сможете звякнуть по модему приятелям и отправиться вместе с ними по неисследованным дебрям виртуальных миров. Игры, рассчитанные на несколько участников, становятся все более популярными. Глава 15-я, "Инструментальные средства", даст вам полный набор инструментов для построения ваших игр. С помощью этих инструментов можно создать текстуры, персонажей, звуковые файлы, планы игрового поля, декорации. В 16-й главе, "Графическое оформление игр", вы получите представление о точке зрения художника. Используя подходы, описанные в этой главе, вы сможете придать игре больший профессионализм и художественную ценность. 17-я глава, "Техника создания параллакса", учит, как заставить удаленные объекты двигаться медленнее, чем приближенные. Эта техника позволит оживить перспективу в ваших играх. В 18-й главе, "Техника оптимизации", демонстрируются технические приемы повышения производительности. Ваша игра будет работать быстрее и движения персонажей станут более плавными, если вы последуете приведенным в этой главе советам. 19-я глава, "Игра Warlock (Колдун)", описывает игру, созданную автором этой книги. - 21 - ГЛАВА 1 (Андрэ Ла Мот) ВИДЕОИГРЫ, ПЕРВЫЕ ШАГИ ... С чего начать? Хочется так много сказать, что невольно придется посвятить этому несколько страниц. То путешествие, которое мы собираемся предпринять в мир разработки видеоигр, можно смело назвать захватывающим приключением. Создание видеоигр можно сравнить с написанием стихов или рисованием картины. Для этого нужно вдохновение, ведь создатель хочет поделиться с окружающим миром частичкой своего воображения. Один великий скульптор сказал однажды: "Статуя была здесь всегда, я просто освободил ее из камня". Это высказывание вполне применимо и к видеоиграм. Компьютер - это просто хранилище битов информации и, устанавливая их в 1 или 0, вы создаете образ. В этом заключается искусство. Я хочу, чтобы вы настроились на созидательную работу.Нам потребуется полное взаимопонимание. В этой главе я расскажу о том, как создаются видеоигры. Вы узнаете вот о чем: - Кто пишет видеоигры; - Откуда берутся идеи; - Фазы создания видеоигры; - Что вы узнаете из этой книги. В следующих главах вы узнаете, как писать игры. - 22 - КТО ПИШЕТ ВИДЕОИГРЫ ? Видеоигры создаются группами совершенно разных людей. Нас объединяет желание сделать нечто, что заставляет смеяться, улыбаться, в азарте подпрыгивать на стуле. Если вы хотите доставить другим радость, то можете не сомневаться, что видеоигры - это то, что надо! Кроме того, создание видеоигр и нас самих делает счастливыми. Я не думаю, что написание компилятора может сделать кого-нибудь счастливым человеком. Видеоигры - это способ выражения самых фантастических идей и образов. Многим просто необходима отдушина для воплощения своих безумных фантазий. Кстати, я думаю, что именно поэтому существует театр и кино. Мы верим в свои иллюзии, порой убегая в них от ужасной реальности. Видеоигры - это такой же способ позволить людям хотя бы на время стать богами своих маленьких вселенных. ОТКУДА БЕРУТСЯ ИДЕИ ? Идеи видеоигр берутся из нашего воображения. Именно в нашем сознании существует бесчисленное количество миров, населенных роботами, и городов, наводненных призраками. Попробуйте покопаться у себя в голове и найти какой-нибудь сюжет. Если ничего не получится - не расстраивайтесь. Сходите в ближайший прокат видео и возьмите несколько фантастических лент - может быть они помогут разыграться вашей фантазии ? Единственное, от чего я хотел бы вас предостеречь - это от переделки чужих игр. Во-первых, это нехорошо, а во-вторых, у вас могут просто появиться серьезные неприятности. В конце концов, ваши сны могут подсказать самые фантастические сюжеты для игр. Когда у вас, наконец, появится идея, то очень важно датьей "отстояться". Попробуйте в течение недели или двух просто выкристаллизовать ее у себя в голове. Пусть она станет для вас совершенно ясной, попробуйте играть в нее в своем воображении. Если это произойдет, то считайте, что самая сложная часть уже позади. Осталось самое простое - запрограммировать ваши идеи. ФАЗЫ СОЗДАНИЯ ВИДЕОИГР Видеоигра, как и любой другой программный продукт, должна создаваться по определенной методике. Это значит, что мы в процессе разработки должны придерживаться определенных правил и рекомендаций. Итак: - Во-первых, нужна идея. Мы уже об этом говорили; - Если есть понимание того, что будет в игре, то есть смысл написать что-то типа сценария. Если игра будет развиваться на нескольких уровнях - опишите каждый из них; - 23 - - Затем вам надо разнообразить каждый из уровней какими-нибудь неожиданными ходами, целями и т.д. Вы должны заинтересовать игрока, заставить его проходить уровень за уровнем в вашей игре; - Если у вас есть понимание каждого уровня игры, то имеет смысл подумать оструктуре самой игры. Как будут себя вести игровые объекты, как они будут взаимодействовать, какие возможности получит игрок? В этот момент у вас уже есть достаточно информации, чтобы садиться и начинать писать более развернутый план игры. Теперь попробуйте чуть более заострить свое внимание на специфике игры. Например: - Выберите, в каком видеорежиме у вас будет работать игра. Например, она может быть выполнена в режиме высокого разрешения, но использовать при этом только несколько цветов. - Подумайте, насколько сложной будет графика. Будет ли она трехмерной или двухмерной. О том, как решать эти проблемы вы также узнаете из данной книги. Когда вы решите для себя эти вопросы, настанет время подумать о тех средствах созидания, которыми мы располагаем. Попробуйте начать конструировать с максимальной детализацией самый первый уровень. У вас сразу появится необходимость в специальных инструментальных средствах. Вот их минимальный набор: - Программа для рисования битовых образов; - Программа для анимации битовых образов; - Си-код для бит-блиттинга /блокового перемещения битовых образов/, изменения видимого размера объектов /масштабирования/ и рисования линий; - Алгоритмы искусственного интеллекта для персонажей игры; - Средства для работы со звуком; - Си-код для работы с устройствами ввода; - Инструменты для рисования уровней и сохранения их на диске; - Набор MIDI-звуков для каждого из уровней. Когда вы начнете писать программу, старайтесь разбить ее на маленькие секции. На самом деле программа может быть разбита на следующие куски: - Игровой мир и описывающие его структуры данных; - Система рендеринга *); - Система ввода/вывода; _____________________ *)Рендеринг - то же, что и визуализация - набор методов вывода изображений на экран - 24 - - Система искусственного интеллекта; - Основной игровой цикл; - Интерфейс пользователя; - Система звука. ЧТО ВЫ УЗНАЕТЕ ИЗ ЭТОЙ КНИГИ ? Эта книга написана, чтобы нвучить читателя создавать трехмерные видеоигры типа DOOM или Wolfenstein 3-D. Эту книгу не стоит рассматривать как учебник по видеографике. Книга написана так, что в ней освещаются вопросы,связанные именно с играми и разработкой игр. Для того чтобы научиться писать игры, нам придется очень серьезно потрудиться, но я уверен, что когда вы закончите работу над этой книгой, то сможете самостоятельно разработать серьезную игру. Эта такая трехмерная игра типа Wolfenstein 3-D. Впрочем, подойдя к последней главе, вы сами ознакомитесь с тем, что у меня получилось. ИТОГ В этой главе мы узнали кое-что о том, как и для чего пишутся видеоигры. Кроме того, выяснилось, что помимо технических навыков для разработки игры нужно иметь: - Сценарий игры; - Распределение по ролям; - Дизайн игры; - Понимание того, для чего вы все это делаете. Итак, вроде,мы обсудили все, что находится вокруг игр. Пора начать работать. Давайте начнем. - 25 - ГЛАВА 2 (Андрэ Ла Мот) ОСНОВЫ ЯЗЫКА АССЕМБЛЕРА Доктор Руди Раккер, мой друг и бывший наставник, как-то сказал: "Ассемблер - это просто круто". Я думаю, эти слова как нельзя лучше подходят к данной главе. Ассемблер - это родной язык всех компьютеров, и если вы им хорошо овладеете, он представит вам фантастические возможности. В настоящей главе мы кратко ознакомимся с этим яззыком. Мы научимся подключать фрагменты, написанные на ассемблере, к нашим программам и использовать встроенный (in-line) ассемблер компилятора Microsoft C. Кроме того, мы пойдем чуть дальше и напишем еще парочку графических процедур. Таким образом, эту главу можно разделить на следующие части: - Зачем нам нужен ассеиблер при написании игр; - Описание семейства процессоров 80х86; - Регистры ЦПУ; - Общий вид процедуры на ассемблере; - Передача параметров; - Локальные переменные; - Создание внешних ссылок; - Возвращение параметров; - 26 - - Некоторые полезные управляющие конструкции; - Установка видеорежимов; - Сверхскоростная очистка экрана; - Использование встроенного ассемблера. ЗАЧЕМ НАМ НУЖЕН АССЕМБЛЕР ПРИ НАПИСАНИИ ИГР ? Даже для таких компьютерных богов, как Microsoft и Borland, сегодня ассемблер намного быстрее программ на Си. Я помню дни, когда все игры были написаны целиком на ассемблере. Вы можете себе это представить? К счастью, сейчас у нас есть куча ученых и программистов, которые заняты разработкой компиляторов. Эти компиляторы дают код, не многим хуже ассемблерного. Но, к сожалению, даже компиляторы чуть-чуть не дотягивают до идеала. Вот почему нам обычно приходится последние 5 процентов делать своими руками. Мы должны применять ассемблер для рисования точек, линий, выполнения заливок, наложений и т.д. Мы вынуждены его применять там, где нужна сверхскорость - в графике. Существует несколько способов включения ассемблера в наши программы: - Можно написать на ассемблере отдельную функцию и вызывать ее из Си. Мы используем этот способ в критичных по времени выполнения участках программы; - Мы можем использовать такую штуку, как встроенный ассемблер. Он позволяет использовать инструкции ассемблера наравне с Си-кодом. Это неоценимая находка для разработчика видеоигр, поскольку вам не надо использовать кучу промежуточных утилит и создавать массу независимых текстов программ. Кроме того, встроенный ассемблер здорово экономит ваше время. Прежде чем двинуться дальше, я хочу предостеречь вас от одной из крайностей - не надо пытаться написать на ассемблере все. Используйте его экономно и только тогда, когда он действительно нужен. В противном случае ваш код будет довольно сложен и не переносим (кстати, втом же DOOM`е на ассемблере написано всего несколько процедур, а все остальное - это эффективный Си-код). Еслиже вам надо написать более двух тысяч строк ассемблера, то лучшим решением будет пересмотр применяемого вашего алгоритма. ОБЗОР СЕМЕЙСТВА ПРОЦЕССОРОВ 80х86 Семейство процессоров 80х86 более чем разнообразно. Фирма Intelсоздала процессоров больше, чем вы в состоянии представить. Игры и программы, - 27 - которые мы будем создавать в этой книге, ориентированы только на 386-, 486- и 586-процессоры. Для наших целей мы будем говорить о реальном режиме работы этих процессоров: режим эмуляции процессора 8086, используемый DOS с 640-килобайтным ограничением. Как вы знаете, первым из этого семейства был 8088. Он стоял на первом ПК. Потом его сменил 8086, но это уже история. Весь мир перешел на DOS, которая изначально была ориентирована на процессор 8086. Производя процессоры 286, 386, 486, 586 (и вроде скоро появится 686), Intel обеспечивал поддержку 8086-процессора. Это значит, что даже на 586 ПК вы неизбежно натолкнетесь в DOSе на 640-килобайтный барьер. На самом деле, это ужасно, поскольку 386 и все последующие 32-разрядные процессоры имеют непрерывную модель памяти(без сегментов), более мощные инструкции и т.д. Использование только новых инструкций сделает наши игры более мощными.Более того, непрерывная модель памяти устранит понятие сегментных регистров, 640-килобайтного барьера и всего, что с ним связано. Мы можем указать компилятору порождать 286-инструкции, дав соответствующую директиву, но это будут лишь 16-битные команды, которые не дадут нам возможности использования всех средств 32-разрядных процессоров типа 386, 486 и 586. Кстати, для удобства я буду называть 386-, 486- и 586-процессоры просто "процессорами". В функции процессора входит перемещение данных в памяти и выполнение с ними некоторых преобразований. К ним относятся математические и логические (например, И, ИЛИ, НЕ) операции, проверка различных условий и т.п. Любой процессор должен иметь достаточно много регистров, чтобы его можно было эффективно программировать (они нужны для сохранения данных). Регистры процессора ПК будут описаны в следующем разделе. РЕГИСТРЫ ПРОЦЕССОРА Регистры общего назначения. *) --------------------------- Данные регистры используются во время выполнения программ и во многих случаях являются взаимозаменяемыми. Кроме того, каждый из них имеет определенное предназначение. АХ - 16 бит, общего назначения, часто именуется аккумулятором; ВХ - 16 бит, общего назначения и индексный; СХ - 16 бит, общего назначения и счетчик; ______________________________ *) Описание регистров в этом разделе соответствует младшим моделям CPU семейства 80х86 и не включает расширения, введенные фирмой Intel начиная с 386 процессора (прим. ред.) - 28 - DX - 16 бит, общего назначения; ВР - 16 бит, общего назначения, используется для хранения смещения и индексов, часто называетсярегистром базы; SI - 16 бит, общего назначения, используется в операциях с памятью (SI source issue -регистр источника, используется для хранения смещения источника при выполнении строковых команд). DI - 16 бит, общего назначения, используется в операциях с памятью (DI - destination issue - регистр приемника, используется для хранения смещения пункта назначения при выполнении строковых команд). Сегментные регистры --------------------- Данные регистры используются как указатели на сегменты. Сегмент - это блок размером в 64 К*, который предназначен для определенных целей: для хранения программного кода, данных и т.д. DS - сегмент данных; CS - сегмент кода; ES - дополнительный сегмент; SS - сегмент стека; IP - счетчик. Флаговый регистр ----------------- Этот регистр сохраняет статусы состояния процессора, такие как: Z (zero - ноль), C (carry - перенос) и т.д. Этот регистр не доступен напрямую, но его содержимое можно узнать с помощью соответствующих инструкций. ОБЩИЙ ВИД АССЕМБЛЕРНОЙ ФУНКЦИИ Процедуры ассемблера очень похожи на См-функции. У них есть начало, конец и код, расположенный в середине. В этой книге мы будем ссылаться на Microsoft macro assembler версий от 5.1 до 6.1 (MASM). Это связано с тем, что в них уже есть директивы, упрощающие стыковку с программами на Си**. Примечание. MASM версии 5,0 подойдет для первого примера, но для успешной работы с книгой вам понадобится версия 5.1 или старше. ________________________ * Абсолютный адрес сегментов выравниваетсяна 16-байтную границу (прим.ред.) ** Аналогичные возможности имеет и ассемблер фирмы Borland - TASM (прим.ред.) - 29 - Прежде чем начать изучение способов передачи параметров и прочих интересных вещей, давайте посмотрим, что нам нужно как минимум для написания внешней программы на ассемблере, которую мы потом вызовем из Си. Итак, Листинг 2.1 покажет нам прототип процедуры на ассемблере. Листинг 2.1. ПРОТОТИП ПРОЦЕДУРЫ ДЛЯ MASM 5.0 И БОЛЕЕ СТАРШИХ ВЕРСИЙ ------------------------------------------------------------------------ ; .MODEL MEDIUM ; тип модели памяти .CODE ; начало кода PABLIC _function_name ; информация для компоновщика. Функция ; может экспортироваться _function_name PROC FAR ; название и тип функции (ближняя или ; дальняя). Ближние функции можно ; использовать для моделей памяти SMALL ; и COMPACT, а дальние применяются для ; моделей памяти MEDIUM, LARGE и HUGE. push BP ; готовим фрейм стека - пролог функции mov BP,SP ; сохраним стек ; Работа функции pop BP ; восстанавливаем фрейм стека - ; эпилог функции _function_name ENDP ; конец процедуры END ; конец кода ____________________________________________________________________________ Давайте проанализируем программу, приведенную в Листинге 2.1. - Первая директива, которую мы встречаем - это .MODEL. Как и компилятор Си, MASM должен знать, какая из моделей памяти используется. Ключевое слово MEDIUM означает, что мы собираемся использовать модель памяти именно типа MEDIUM. Теперь я хочу напомнить вам свойства основных моделей памяти: Модель SMALL имеет один 64-килобайтный сегмент для кода и один сегмент для данных; Модель COMPACT имеет один 64-килобайтный сегмент для кода и несколько сегментов данных; - 30 - Модель MEDIUM имеет один 64-килобайтный сегмент для данных и несколько сегментов для кода; Модель LARGE имеет несколько сегментов как для кода, так и для данных; Модель HUGE разрешает данным быть больше, чем 64 К, но в остальном полностью похожа на модель LARGE. Чаще всего мы будем использовать модели памяти MEDIUM и LARGE. - Следующая директива - PUBLIC. Она говорит MASM, что следующее имя будет экспортировано, то есть станет "видимо" из других модулей; - Теперь мы переходим к началу самой функции. В ассемблере функция начинается с директивы PROC, которая следует сразу за именем функции; - В этом месте мы находимся внутри исполняемой части кода. Первые две инструкции устанавливают стек таким образом, что процедура получает доступ к параметрам, передаваемым через стек. К этому мы еще не раз вернемся; - В конце процедуры мы очищаем стек; - В конце каждой процедуры ставится ключевое слово ENDP; - В одном блоке мы можем иметь сколько угодно процедур, но надо помнить, что самой последней должна быть директива END. Она сообщает ассемблеру об окончании программы. Тема следующего разговора - это передача параметров. Наши ассемблерные функции не были бы так полезны, если б не имели возможности обмениваться с ними информацией. ПЕРЕДАЧА ПАРАМЕТРОВ Языки Си и ассемблер похожи на дальних родственников, живущих в одном доме - они вынуждены придерживаться сложных взаимных условностей. Однако ассемблер значительно более примитивен. Поэтому припередаче параметров ассемблерной процедуре нам приходится сочинять множество дополнительных строк кода, обеспечивающих доступ к ним. Вначале необходимо оформить фрейм стека, как показано в листинге 2.1. Далее необходимо получить доступ к переданным параметрам, основываясь на новом значении регистра базы (ВР). Для обеспечения доступа к параметрам вы должны четко представлять себе, как именно передаваемые параметры размещаются в стеке. К примеру, вы хотите написать процедуру, вычисляющую сумму двух чисел и возвращающую результат в регистре АХ. На языке Си описание этой фонкции выглядит так: int Add_Int(int number_1, int number_2); При выполнении этой процедуры компилятор языка Си создаст фрейм стека и поместит туда параметры. Иными словами, значения number_1 и number_2 -31 - ┌───────────────────────────────────┐ │ . │  │ . │ │ │ . │ Увеличение ├───────────────────────────────────┤ адресов ВР+6 │ Аргумент_2 │ ├───────────────────────────────────┤ ВР+4 │ Аргумент_1 │ ├───────────────────────────────────┤ ВР+2 │ Адрес возврата │ │ (2 байта) │ ├───────────────────────────────────┤ ВР ──── │ Исходное значение │ │ ВР │ ├───────────────────────────────────┤ ВР-2 │ Локальные переменные │ ├───────────────────────────────────┤ ВР-4 │ Локальные переменные │ └───────────────────────────────────┘ Ближний вызов соглашения Си Рис.2.1. Фрейм стека при ближнем вызове. ┌──────────────────────────────────┐ │ . │ │ . │ │ . │ ├──────────────────────────────────┤ ВР+8 │ Аргумент_2 │ ├──────────────────────────────────┤ ВР+6 │ Аргумент_1 │ ├──────────────────────────────────┤ │ Адрес возврата │ ВР+4 │ (сегмент) │ ├──────────────────────────────────┤ │ Адрес возврата │ ВР+2 │ (смещение) │ ├──────────────────────────────────┤ ВР ───── │ Исходное значение │ │ ВР │ ├──────────────────────────────────┤ ВР-2 │ Локальные переменные │ ├──────────────────────────────────┤ ВР-4 │ Локальные переменные │ └──────────────────────────────────┘ Дальний вызов соглашения Си Рис.2.2. Фрейм стека при дальнем вызове. - 32 - будут расположены в стеке. Вы можете подумать, что сначала в стек будет помещено значение number_1, а затем - number_2. Однако компилятор Си думает несколько иначе. Он помещает параметры в стек в обратном порядке, что облегчает доступ к ним. За счет применения оьратного порядка размещения параметров, адрес каждого из них будет задаваться некоторым положительным смещением относительно регистра ВР, что делает жизнь намного легче. В часности, именно благодаря такому механизму, некоторые функции (например, printf) могут получать переменное число параметров. Таким образом, при вызове функции Add_Int фрейм стека будет выглядеть, как показано на рисунке 2.1 или 2.2, в зависимости от используемой модели памяти. Причина, по которой вид фрейма стека зависит от модели памяти, состоит в следующем: при вызове процедуры в стек помещается адрес команды, следующей непосредственно за командой вызова. Если мы применили модель памяти SMALL, все процедуры по определению находятся внутри одного кодового сегмента. Следовательно, для доступа из программы к любой из них нам необходимо знать только смещение. Как известно, значение смещения занимает 2 байта. Если же мы применяем модель памяти MEDIUM или LARGE, то должны сохранить как смещение, так и сегментную часть адреса. Вместе сегмент и смещение занимают уже целых 4 байта. Как видно из рисунков 2.1 и 2.2, параметры помещаются в стек в том порядке, который обеспечивает их адресацию положительными смещениями относительно значения регистра базы (ВР). Следовательно, для доступа к параметру number_1 вы должны использовать [ВР+4] или [ВР+6], в зависимости от установленной модели памяти. В качестве примера рассмотрим полный текст функции Add_Int. Она вычисляет сумму двух передаваемых в качестве аргументов чисел. Результат возвращается в регистре АХ, который, в соответствии с соглашениями языка Си, используется для возврата 16-битных значений. Листинг 2.2. ПРОСТАЯ ПРОЦЕДУРА СЛОЖЕНИЯ. ------------------------------------------------------------------------ ; Секция констант integer_1 EQU [BP+6] ; задает адрес первого аргумента integer_2 EQU [BP+8] ; задает адрес второго аргумента .MODEL MEDIUM ; указывает компилятору, что он должен ; использовать модель памяти MEDIUM .CODE ; начало кодового сегмента PUBLIC _Add_Int ; эта функция - общедоступна _Add_int PROC FAR ; имя функции и ее тип (дальняя) push BP ; эти две инструкции инициализируют ; фрейм стека mov BP,SP mov AX,integer_1 ; помещаем первое слагаемое ; в аккумулятор (регистр АХ) - 33 - add AX,integer_2 ; добавляем второе слагаемое ; к содержимому АХ pop BP ; ликвидируем фрейм стека _Add_Int ENDP ; конец процедуры END ; конец кодового сегмента _________________________________________________________________________ Единственное, что мы изменили по сравнению с Листингом 2.1, это добавили несколько строк кода и ввели определения для адресов параметров. Теперь давайте проанализируем то, что у нас получилось. - Как и в предыдущем листинге, здесь были использованы директивы ассемблера для указания модели памяти, способа вызова, начала и конца функции; - EQU - это простая директива, заменяющая одну строку на другую. Я прибег к ней потому, что мне не хотелось в тексте самой функции использовать синтаксические конструкции [BP+6] и [BP+8]. Строки, задающие выражения, которые будут подставлены при компиляции, это: integer_1 EQU [BP+6] integer_2 EQU [BP+8] В общем, использование таких подстановок позволяет сделать ассемблерную программу более читабельной. Единственной альтернативой такому подходу является написание команды индексирования относительно содержимого одного из регистров (типа [BP+6]). ДИРЕКТИВА USES Надо сказать, что ассемблер MASM, начиная с версии 5.1, имеет некоторые новые директивы, упрщающие порядок передачи параметров и создания фрейма стека. Для этого вы можете использовать директиву USES вместе с директивой PROC. Они сообщают ассемблеру, какие именно регистры будут использоваться в функции. Директива USES оберегает вас от всей рутины, связанной с определением стекового фрейма и подстановками переменных. Более того, она генерирует код пролога и эпилога для сохранения регистров, которые вы указали для использования в функциях. Таким образом, содержимое этих регистров не будет изменено, когда процедура вернет управление вызвавшей ее Си-функции. ┌──────────────────────────────────────────────────────────────────────────┐ │ Внимание ! │ │ ----------- │ │ Помните, что Си и ассемблер используюи одни и те же регистры процессора.│ │ Если вы пользуетесь регистром в ассемблерной программе, то должны его │ │ сохранить в стеке и восстановить перед завершением функции. Иначе ваша │ │ Си-программа может просто "сломаться" в момент выхода из вызова │ │ ассемблерной вставки. │ └──────────────────────────────────────────────────────────────────────────┘ - 34 - Директива PROC и относящийся к ней уточнитель USES имеет следующий синтаксис. label PROC [[attributes]] [[USES register_list]] [[,]] [[parameter_list][:type]]...]] - Поле label - это имя прцедуры; - Поле attributes сообщает ассемблеру свойства вашей процедуры. Она может содержать множество параметров, таких как тип процедуры (NEAR или FAR), "видимость процедуры (PUBLIC или PRIVATE) и, наконец, тип языка (C, PASCAL и т.д.). Эта возможность делает наши программы на ассемблере более читаемыми. Правда, это связывает руки, но зато программы обретают определенную элегантность; - Поле register_list показывает, какие регистры будет использовать функция. При этом ассемблер генерирует код, который может сохранить их на время работы прцедуры и восстановить при выходе; - Поле parameter_list очень похоже на список параметров в Си; - Для каждой передаваемой процедуре переменной должен быть указан тип, определяющий их размер (например, BYTE или WORD). Тип задается в поле type. Если вы пишите процедуру, в которую передаете три целых величины, и будете использовать регистры SI, DI и CX, то должны включить следующий оператор: far proc USES SI DI CX, integer_1:WORD, integer_2:WORD, integer_3:WORD Используя директивы PROC и USES, давайте перепишем процедуру из Листинга 2.2. Листинг 2.3. МОДИФИЦИРОВАННАЯ ВЕРСИЯ Add_Int. ________________________________________________________________________ .MODEL MEDIUM, C ; использовать модель MEDIUM ; и соглашения по вызову Си .CODE ; начало кода PUBLIC _Add_Int ; объявляем функцию как общедоступную _Add_Int PROC USES integer_1:WORD, integer_2:WORD mov AX,integer_1 ; загрузить первый операнд в АХ - 35 - add AX,integer_2 ; сложить второй операнд с АХ _Add_Int ENDP ; конец процедуры END ; конец кода ________________________________________________________________________ Как видно из Листинга 2.3, тяжкое бремя сохранения регистра ВР, создания и уничтожения стекового фрейма теперь отдано на откуп ассемблеру. Более того, мы получили прямой доступ к параметрам integer_1 и integer_2. ПЕРЕДАЧА УКАЗАТЕЛЕЙ Мы знаем, как передать значения таких параметров как BYTE или WORD, но как передать указатель? Указатели передаются как двойные слова, или DWORD. Для доступа к указателям в стеке нам придется воспользоваться старым приемом: разобьем двойное слово указателя на две переменные segment и offset, которые будут иметь тип WORD, или уже к ним будем обращаться в нашей ассемблерной программе*. К примеру, если мы вызываем ассемблерную функцию в модели MEDIUM, (скажем, это будет вызов типа FAR) в следующей строке: pfoo(&x) то получить адрес переменной Х можно будет с помощью следующих подстановок: offset EQU [BP+6] segment EQU [BP+8] Если мы захотим изменить значение Х, то нам придется сделать следующее: mov DI,offset mov AX,segment mov ES,AX mov ES:[DI],CX Эта программа состоит из двух основных частей: _________________________ * Использование приемов передачи указателей, описанных в этом разделе, имеет смысл только для моделей памяти COMPACT, LARGE и HUGE или для случая, когда переменная, адрес которой передается ассемблерной процедуре, размещена в памяти, выделенной функциями farcalloc или farmalloc. Глобальные и локальные переменные программы в случае использования других моделей памяти размещены внутри одного сегмента данных и, следовательно, в этом случае нет необходимости передавать сегментную часть адреса. Иными словами, функция pfoo в Си описана как принимающая дальний указатель, например, pfoo(void far *Ptr) (прим.ред.) ____________________ - 36 - - Во-первых, создается указатель на Х через регистры ES и DI; - Во-вторых, изменяется значение переменной Х. Ну вот и все о том, что связано с передачей параметров. Новые расширения директив PROC и USES просто великолепны, и вы можете всегда ими пользоваться, если чувствуете от их применения комфорт. Если вы предпочитаете все делать в стиле MASM 5.0, то это ваше право. С точки зрения быстродействия программы здесь нет никакой разницы. ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ Теперь вы знаете, как передавать переменные в процедуры, а вот как насчет временных и локальных переменных, которые действуют только внутри процедуры? Когда мы пишем программы на Си, то применяем локальные переменные для вычислений, сохранения результатов и т.д. Так же, как и в Си, мы можем создать локальные переменные в наших ассемблерных функциях, используя стек. Конечно, можно создать локальные переменные в области данных (например, в сегменте данных), но этого не стоит делать. Почему? Дело в том, что стек как раз и создавался для временного хранения данных и локальных переменных, так что именно им и стоит пользоваться. Давайте посмотрим как это делается. Когда мы определяем стек, сохраняя регистр ВР, то можем извлекать параметры путем прибавления положительного смещения к регистру ВР, например, [BP+6] и т.д. Таким образом, получается, что в действительности стек - это область непрерывной памяти, которую мы можем использовать также и для хранения локальных переменных. Для этого нам надо только использовать отрицательное смещение относительно регистра ВР. В случае, если мы хотим иметь две локальные переменные local_1 и local_2, можно использовать следующую подстановку: local_1 EQU [BP-2] local_2 EQU [BP-4] Это дает нам два целых числа. Пока нам не известно, что записано по этим адресам и мы можем только предполагать, что данный участок памяти можно использовать безболезненно. Однако нужно помнить, что мы только что использовали стек для хранения данных и теперь нам необходимо самим изменить регистр SP для отражения этого. Если этого не сделать, то первая встретившаяся инструкция PUSH обязательно что-нибудь запишет в нашу переменную и непременно "испортит" ее. Для исключения этой ситуации можно предложить уменьшить значение регистра SP на длину переменных, которые мы хотим сократить. В нашем случае это составит 4 байта. В Листинге 2.4 показано, как это делать. - 37 - Листинг 2.4. КОРРЕКТИРОВКА РЕГИСТРА SP. ______________________________________________________________________ push BP ; устанавливаем фрейм стека, как обычно mov DP,SP sub SP,4 ; корректируем указатель стека. Теперь наши ; переменные не будут случайно изменены ; Вся работа выполняется здесь add SP,4 ; перед уничтожением фрейма стека надо восста- ; новить исходное значение указателя стека pop BP ; уничтожаем фрейм стека, ; восстанавливая регистр BP _____________________________________________________________________________ Директива LOCAL ---------------- Заметьте, что в Листинге 2.4 мы изменили значение регистра SP не только в начале процедуры, но и в конце (перед тем, как восстановить регистр ВР). Эта техника обычно используется для размещения переменных в ассемблерных процедурах при их вызовах из языков высокого уровня. В Листинге 2.4 это делалось вручную. А вот MASM 5.1 и более поздние версии имеют встроенную директиву, которая выполняет это автоматически. Это директива LOCAL и она имеет следующий синтаксис: LOCAL variable_name: type, variable_name: type, ... (Любопытно. MASM все больше становится похож на Си. К чему бы это?) Давайте теперь напишем программу с использованием директивы LOCAL. Она называется Timer и требует одного параметра - time, который затем помещает в локальную переиенную asm_time. Из Си этот вызов будет выглядеть так : Timer(25); Листинг 2.5 показывает реализацию программы Timer на ассемблере, используя все диретивы, которые мы обсудили в этой главе. Листинг 2.5. ПРОГРАММА Timer. ________________________________________________________________________ .MODEL MEDIUM ; используем модель MEDIUM .CODE ; начало кодового сегмента ; в процессе работы функция меняет содержимое регистра AX _Timer PROC FAR USES AX, time:WORD LOCAL asm_time :WORD - 38 - mov AX, time mov asm_time, AX _Timer ENDP END ___________________________________________________________________________ Эта программа оказалась бы куда длиннее, если бы мы не использовали новые диретивы MASM. Правда, если у вас есть только MASM версии 5.0, то вы можете обойтись и без них. Совет ---------- Я надеюсь, что вы создадите свои шаблоны, позволяющие обращаться к передаваемым параметрам и локальным переменным. СОЗДАНИЕ ВНЕШНИХ ССЫЛОК Когда вы пишите модуль на Си, в котором встречаются переменные или функции, определенные в других модулях, вы должны использовать ключевое слово EXTERN, сообщающее компилятору, что переменные или функции будут определены позже (на этапе компоновки). MASM 5.0 и более старшие версии также поддерживают эту возможность. В наших ассемблерных функциях может понадобиться передать значение глобальной переменной обратно в вызывающую Си программу. В принципе, мы можем ее передавать как параметр каждый раз и не думать о веншних переменных. Но данный способ критичен по времени и весьма архаичен. В видеоиграх, как ни в каких других программах, мы должны использовать как можно больше глобальных переменных. Причина очевидна - скорость выполнения. Нет смысла терять драгоценные такты процессора на выполнение команд PUSH и POP в момент вызова функций. Синтаксис директивы EXTRN следующий: EXTRN symbol: type, symbol: type, ... где symbol - имя переменной, а type - ее размер (например, BYTE, WORD, DWORD). Директива EXTRN разрешает разместить переменную в вашем Си-коде и получить к ней доступ через параметры. Это имеет и обратную сторону: переменная, обозначенная как EXTRN означает, что она занимает текущий сегмент данных, адресуемых через регистр DS. Если вы все будете делать в - 39 - модели SMALL или MEDIUM, то не стоит беспокоиться, если же вы работаете в модели LARGE, то никто не гарантирует, что вы получите доступ к вашей глобальной переменной, используя текущее значение регистра DS. Чтобы избежать этого, всегда применяйте модели SMALL и MEDIUM. Давайте для примера напишем процедуру, которая складывает два целых числа и помещает результат в третье. Фокус заключается в том, что все эти величины будут глобальными и по отношению к ассемблерной процедуре - внешними. Листинг 2.6 демонстрирует код Си для этой программы, а в Листинге 2.7 показана ее реализация на ассемблере. Листинг 2.6. Си-часть примера. ------------------------------------------------------------------------ #include int first = 1, second = 2, third = 0; // Это те числа, с которыми // мы хотим работать void main (void) { printf ("\nBefore adding third = %d",third); Add_Ext(); // Вызываем ассемблерную процедуру printf("\nAfter adding third = %d",third); } // конец функции main __________________________________________________________________________ Листинг 2.7. Ассемблерная часть примера _________________________________________________________________________ .MODEL MEDIUM ; будем использовать MEDIUM модель EXTRN first:WORD, second:WORD, third:WORD .CODE ; начало кодового сегмента _Add_Ext PROC FAR ; процедура имеет тип FAR (дальняя) mov AX, first ; помещаем первое число в аккумулятор add AX, second ; прибавляем второе число mov third, AX ; помещаем результат в переменную third _Add_Ext ENDP ; конец процедуры END ; конец кодового сегмента _________________________________________________________________________ Листинги 2.6 и 2.7 - это примеры использования внешних переменных first, second и third. Программа вызывает Add_Ext, которая складывает - 40 - переменные first и second и сохраняет результат в third. Это подводит нас к теме возврата результатов обратно в программу на Си, которая вызывала ассемблерную процедуру. ВОЗВРАЩЕНИЕ ПАРАМЕТРОВ В ВЫЗЫВАЮЩУЮ ФУНКЦИЮ Когда Си только начинал создаваться, одним из требований в спецификациях называлась "функциональность" языка. Под функциональностью я понимаю возвращение результата и возможность использования комплексных выражений. Например, рассмотрим следующее выражение на Си: coeff = Factorial(n)*cos(r)*Scale(z); Это выражение использует три функции и выполняет требуемые математические операции. Результат сохраняется в переменной coeff. Именно это и делает Си "функциональным" языком. Если вы скомпилируете парочку Си-функций, возвращающих значение, и посмотрите на их ассемблерные листинги, то вскоре заметите, что функции всегда возвращают результат в строго определенном наборе регистров. Если же вы прислушаетесь к моим советам, то я гарантирую, что ваши ассемблерные функции будут, по крайней мере, правильно возвращать результаты. В зависимости от типа, возвращаемые в Си параметры должны находиться в следующих регистрах: - BYTE возвращается в регистре AL; - WORD возвращается в регистре AX; - DWORD должно возвращаться в паре DX:AX, причем в AX записывается младшее слово; - Указатели типа NEAR должны возвращаться в регистре AX; - Указатели типа FAR возвращаются в паре DX:AX, причем в DX должен содержаться сегмент, а в AX - смещение. Давайте для примера вспомним Листинг 2.2, где мы складывали два целых числа и накапливали результат в AX. К счастью, это именно тот регистр, в котором целое значение может быть возвращено в Си-программу. Если же встречается другая ситуация, (например, результат находится в регистре СХ), то для корректной передачи результата мы должны перед выходом переместить полученное значение в АХ. - 41 - Так... Вроде бы, с директивами и техникой программирования на ассемблере MASM мы покончили. Теперь я предлагаю перейти к более живым примерам. Кстати, для них-то все это и написано. УСТАНОВКА ВИДЕОРЕЖИМОВ В играх есть немало технических хитростей: работа со звуком, "искусственный интеллект" и многое другое. Но перед тем, как начать этим заниматься, давайте попробуем инициализировать наш дисплей. Конечно, мы сразу можем набрать гору документации с подробным описанием устройства дисплея, регистров и установок, но все это весьма опасно, и вот почему. То, что будет работать на одной видеокарте, может оказаться абсолютно неработоспособным на другой. Таким образом, чтобы избежать возможной несовместимости, для установки видеорежима мы будем использовать базовую систему ввода/вывода (BIOS). Можно смело сказать, что основная графическая мощь ПК сосредоточена в прерывании с номером 10h. Использование этого прерывания весьма просто - необходимо правильно установить нужные регистры процессора в зависимости от выполняемой функции. В этой книге мы будем пользоваться режимом 13h (это графический режим с разрешением 320х200 точек, при 256 цветах). Теперь нам нужно найти, как перевести компьютер в этот режим. Для этого давайте напишем программу на ассемблере для установки режима 13h и программу на Си для проверки. Соответствующие фрагменты показаны в Листингах 2.8 и 2.9. Листинг 2.8. Ассемблерная процедура, устанавливающая видеорежим (SETMODEA.ASM). __________________________________________________________________________ .MODEL MEDIUM, C ; модель памяти - MEDIUM, соглашения ; языка Си .CODE ; начало кодового сегмента PUBLIC Set_Mode ; объявляем функцию как общедоступную Set_Mode PROC FAR C vmode:WORD ; функция получает один параметр mov AX,0 ; функция 0 прерывания 10h - установка ; режима mov AL,Byte PTR vmode ; номер режима, который вы хотите ; установить int 10h ; используем BIOS для установки режима ret ; возврат из процедуры Set_Mode ENDP ; конец процедуры END ; конец кодового сегмента __________________________________________________________________________ - 42 - Листинг 2.9. Си-функция, тестирующая видеорежим (SETMODEC.C). __________________________________________________________________ #include #define VGA256 0x13 #define TEXT_MODE 0x03 extrn Set_Mode (int mode); void main (void) { // устанавливаем режим 320х200 точек, 256 цветов Set_Mode(VGA256); // ждем нажатия любой клавиши while(!kbhit()) {} // возвращаем компьютер в текстовый режим Set_Mode(TEXT_MODE); } //конец ф-ции main __________________________________________________________________ Теперь если вы наберете и запустите эти программы, то, скорее всего, увидите пустой экран. Правда, в данном случае это не означает "зависание" комльютера, а свидетельствует о том, что VGA-карта переключилась в режим 13h. Стоит только нажать любую клавишу, и вы вновь окажетесь в привычном текстовом режиме 80х25. Конечно, можно было бы использовать функцию _setvideomode() из графической библиотеки Microsoft C, но наша функция работает в сотни раз быстрее. Теперь, когда мы научились переключать экран в графический режим, неплохо бы попробовать его очистить. СВЕРХСКОРОСТНАЯ ОЧИСТКА ЭКРАНА Экран в режиме 13h отображается в области памяти, начиная с адреса A000:0000 и по A000:FBFF. При этом каждый пиксель задается одним байтом. Давайте посмотрим на рис.2.3, чтобы лучше понять, как это происходит. В данной конфигурации каждый пиксель может принимать одно из 256 значений, но эти значения не являются, как можно было бы подумать, кодом цвета пикселя. Они представляют собой видимое значение цвета. Теперь поговорим о том, как это работает. Значение пикселя, а это байт, адрес которого определяет положение пикселя на экране, используется в качестве индекса в гигантской таблице цветов. Таким образом, значение пикселя 26 не означает цвет номер 26. Наоборот, это значит "индекс, указывающий на 26-й элемент таблицы и использующий значение этого поля". - 43 - A000:0000 320 байт на строку A000:013F ┌────┬────┬────┐ ┌────┬────┬────┬────┬────┬── ─┬────┬────┬────┐ │R0 │G0 │B0 │ │ 3 │ 8 │100 │ │ │ . │ │ │ │ ├────┼────┼────┤ ╔════╗────┼────┼────┼────┼── ─┼────┼────┼────┤ . . . ┌── ║ 11 ║ 50 │ 20 │ │ │ . │ │ │ │ ├────┼────┼────┤ │ ╚════╝────┼────┼────┼────┼── ─┼────┼────┼────┤ │R11 │G11 │B11 │──┘ │ │ │ │ │ │ . │ │ │ │ ├────┼────┼────┤ ├────┼────┼────┼────┼────┼── ─┼────┼────┼────┤ │ │ │ │ │ │ │ │ │ │ . │ │ │ │ . . . ├────┴────┴────┴────┴────┴── ─┴────┴────┴────┤ . . . По одному байту на пиксель . . . 200 . . . . . . . . . . . строк . . . ├────┬────┬────┬────┬────┬── ─┬────┬────┬────┤ │ │ │ │ │ │ │ │ │ │ . │ │ │ │ ├────┼────┼────┤ ├────┼────┼────┼────┼────┼── ─┼────┼────┼────┤ │R255│G255│B255│ │ 5 │111 │ 206│ │ │ . │ │ │ │ └────┴────┴────┘ └────┴────┴────┴────┴────┴── ─┴────┴────┴────┘ Таблица цветов A000:F8C0 A000:F9FF Рис.2.3. Видеобуфер VGA. Примечание ---------- Таблица цветов содержит по одному байту для каждого из первичных цветов. Однако реально используются только 6 первых бит каждого байта. Таким образом, каждый из элементов таблицы состоит из 3-х байтов, определяющих значения трех основных цветов: красного (R - Red), зеленого (G - Green) и голубого (B - Blue), которые в сумме позволяют представить 262114 цветов.Однако размер таблицы ограничен 256-ю элементами, поэтому и на экране может одновременно присутствовать не более 256 цветов. _________________ В видеоиграх, которые нас привлекают, экран перерисовывается от 15 до 30 раз в секунду. Таким образом, перед тем как нарисовать что-то новое на экране, нам необходимо удалить старое изображение. Для того чтобы это сделать, надо найти способ быстрого заполнения видеобуфера каким-нибудь значением цвета, например, цветом фона. Это значение будет заполнять всю видеопамять, а следовательно, и весь экран в виде цвета. Самый быстрый способ сделать это - воспользоваться ассемблерной инструкцией STOSW. Вы можете спросить: "А зачем использовать - 44 - STOSW, когда каждый пиксель - это байт, в то время как STOSW оперирует со словами (WORD)?". На этот вопрос можно дать 2 ответа: - Во-первых, коль мы можем записать 1 байт, в видеопамять, то значит, можем записать и два; - Во-вторых, нам надо минимизировать количество обращений к видеопамяти, поскольку она работает примерно в 10 раз медленнее, чем обычная память. Поэтому предпочтительнее писать не по одному байту, а сразу по два. Листинг 2.10 показывает ассемблерную функцию для заполнения экрана определенным цветом, а Листинг 2.11 содержит программу на Си, тестирующую ее. Листинг 2.10. Процедура, заполняющая экран (FILLA.ASM). ________________________________________________________________________ screen_ram EQU 0A000h ; видеопамять в этом режиме начинается ; по адресу A000:0000h .MODEL MEDIUM, C ; устанавливаем модель памяти MEDIUM, ; соглашения по вызову языка Си .CODE ; начало кодового сегмента PUBLIC Fill_Screen ; объявляем процедуру общедоступной Fill_Screen PROC FAR C color:WORD ; функция принимает один параметр mov AX,screen_ram ; ES:DI должно указывать на видеопамять mov ES,AX xor di,di ; обнуляем DI mov CX,320*200/2 ; количество слов, которое надо вывести mov AL,BYTE PTR color ; помещаем в регистр AL код цвета mov AH,AL ; этот же код помещаем в регистр AH rep STOSW ; эта команда заполняет видеопамять ; выбранным цветом с максимально ; возможной скоростью RET ; выход из процедуры Fill_Screen ENDP ; конец процедуры END ; конец кодового сегмента _________________________________________________________________________ - 45 - Листинг 2.11. Программа на Си для тестирования программы 2.10 (FILLC.C). _________________________________________________________________________ #include #define VGA256 0x13 #define TEXT_MODE 0x03 extrn Set_Mode(int mode); extrn Fill_Screen(int color); void main(void) { int t; // устанавливаем режим 320х200 точек, 256 цветов (режим 13h) Set_Mode(VGA256); // заполняем экран цветом с кодом 1 (в палитре, устанавливаемой // по умолчанию, это соответствует синему цвету) for (t=0;t<1000; t++) Fill_Screen(1); // ждем нажатия любой клавиши while(!kbhit()) {} // возвращаемся в текстовый режим работы экрана Set_Mode(TEXT_MODE); } // конец функции main __________________________________________________________________________ Эти программы чистят экран с максимальной скоростью. Примечание ---------- Я произвел замер скорости работы этих функций на своем компьютере и получил значение 22 кадра в секунду. Это представляется невероятно медленным, и я сильно забеспокоился. Однако при ближайшем рассмотрении выяснилось, что причина задержки - крайне низкое быстродействие видеопамяти. Собственно, процессор мог бы обеспечить скорость до 250 кадров в сек. Однако, увы, он часто вынужден ждать, пока видеопамять соизволит откликнуться на его обращение. ________________ На прилагаемой к этой книге дискете вы найдете программу под названием GAUGE.EXE. Вы можете использоватьее для замера производительности вашей видеосистемы. - 46 - Наш курс ассемблера проходит отлично. Я уже сам узнал кучу нового и надеюсь, что вы тоже. Теперь нам осталось узнать еще про одну возможность программирования на ассемблере: об использовании встроенного (in-line) ассемблера. ИСПОЛЬЗОВАНИЕ ВСТРОЕННОГО (IN-LINE) АССЕМБЛЕРА Текущая тенденция компьютерной индустрии - использование одного языка программирования. Встроенный ассемблер является откликом на это пожелание. Это платформно-ориентированное расширение стандарта Си. Оно позволяет программистам включать инструкции ассемблера прямо в их программы, а не создавать самостоятельные процедуры и транслировать их отдельно. Это имеет два преимущества: - Во-первых, программисту на Си теперь не нужно знать так много обо всем, как прежде; - Во-вторых, встроенный ассемблер - это максимальное удобство в программировании на разных языках. Встроенный ассемблер объявляется следующим образом: _asm { инструкции ассемблера ; } Это все, что надо: ни директив, ни пролога, ни эпилога. Только одно ключевое слово _asm, да пара скобок - и мы у цели. В связи с использованием встроенного ассемблера на ум сразу приходит миллион вопросов. Я попытаюсь ответить, по крайней мере, на половину из них. Шутка. Кстати, есть пара очень важных моментов: - Не надо заботиться о сохранении регистров - это сделают за вас; - Передача параметров очень упрощена. К переменным, находящимся в области видимости функции, содержащей ассемблерный код, вы вполне можете из ассемблерного блока обратиться по имени так же, как и из любого другого блока функции. Например, если мы хотим обменять два значения с помощью встроенного ассемблера, то следующий Си-код показывает, как это сделать: - 47 - void Swap(int num_1, int num_2) { _asm } mov AX,num_1 mov BX,num_2 mov num_1,BX mov num_2,AX } } После того,как эта процедура произведет обмен, содержимое рабочих регистров окажется полностью разрушенным. Но об этом не стоит беспокоиться, так как компилятор сам позаботится об их сохранении. Для большинства задач возможностей встроенного ассемблера вполне достаточно. Конечно, список его функциональных возможностей намного меньше, чем у MASM, однако в большинстве случаев их вполне хватает. К специалистам в написании компьютерных игр часто обращаются с вопросом: "А насколько быстро это работает?" Так вот, встроенный ассемблер работает практически так же быстро, как и "настоящий". В большинстве случаев вы не увидите абсолютно никакой разницы. Единственное, в чем встроенный ассемблер уступает MASMу, так это в скорости входа и выхода из функции, поскольку он содержит обязательные для Си-функций пролог и эпилог. Тем не менее, я призываю вас использовать встроенный ассемблер. Например, поддержка звуковых карт написана мною именно на встроенном ассемблере и мне на это понадобилось всего несколько часов. При использовании настоящего ассемблера мне пришлось бы потратить примерно в 2 раза больше времени за счет использования отдельных программ для компиляции, линковки и отладки. Что во встроенном ассемблере действительно ужасно, так это ограничения, накладываемые на совместимость с другими компьютерными платформами. Однако подумаем, так ли это страшно, если мы решили писать программы именно для IBM ? И, наконец, маленькая дополнительная информация. Раньше мы решили, что будем использовать процессоры старших поколений только как быстрый 8086. Однако, если вы хотите, весьма неплохо использовать во встроенном ассемблере инструкции процессора 80286, соответствующим образом изменив настройки компилятора. Конечно, можно также использоватькоманды процессоров 386, 486, 586. Только прежде подумайте: если вы будете ориентироваться на процессор 586, не окажется ли потенциальный рынок для вашей игры слишком ограниченным... - 48 - ИТОГ Эта глава многому нас научила. За короткое время мы узнали уйму нового: - Мы изучили архитектуру семейства процессора 80х86; - Мы узнали, как использовать новые директивы макроассемблера для облегчения написания ассемблерных процедур; - Мы узнали, как вызывать функции на ассемблере из Си-программ; - Наконец, мы написали несколько программ, которые переводят компьютер в режим 13h и посмотрели, как организован их вызов из Си. Теперь вы стали намного в более близких отношениях с MASMом и языком ассемблера для ПК вообще. Мы обсудили все основные темы, которые важны при программировании видеоигр, включая интерфейс с языком Си и встроенный ассемблер. Без сомнения, мне надо было научить вас основам программирования на ассемблере. Вы должны уметь использовать ассемблер для решения разных задач. В этой книге мы еще не раз с ним столкнемся, и вы должны быть уверены, что не утонете в море единиц и нулей. Если вы считаете, что еще не все поняли, то лучше возьмите и перечитайте какую-нибудь хорошую книгу об ассемблере, а уж потом переходите к изучению следующей главы.